import { BaseAgent, BaseAgentConfig, AgentCommandConfig } from "./base";
import { ModelConfig } from "./utils";
import {
  OpenCodeConfig,
  OpenCodeResponse,
  OpenCodeStreamCallbacks,
  Conversation,
  ModelProvider,
  AgentType,
} from "../types";

export class OpenCodeAgent extends BaseAgent {
  private providerApiKey: string;
  private provider: ModelProvider;
  private model?: string;

  private escapePrompt(prompt: string): string {
    // Comprehensive escaping for bash double quotes - production ready
    return prompt
      .replace(/\\/g, '\\\\')    // Escape backslashes FIRST
      .replace(/"/g, '\\"')      // Escape double quotes
      .replace(/'/g, "'\\''")    // Escape single quotes (close quote, escaped quote, reopen)
      .replace(/\$/g, '\\$')     // Escape dollar signs
      .replace(/`/g, '\\`')      // Escape backticks (command substitution)
      .replace(/!/g, '\\!')      // Escape exclamation (history expansion)
      .replace(/\n/g, '\\n')     // Escape newlines
      .replace(/\r/g, '\\r')     // Escape carriage returns
      .replace(/\t/g, '\\t');    // Escape tabs
  }

  constructor(config: OpenCodeConfig) {
    const baseConfig: BaseAgentConfig = {
      sandboxProvider: config.sandboxProvider,
      secrets: config.secrets,
      sandboxId: config.sandboxId,
      workingDirectory: config.workingDirectory,
    };

    super(baseConfig);

    // Support providerApiKey field
    this.providerApiKey = config.providerApiKey || "";
    this.provider = config.provider || "openai";
    this.model = config.model;

    if (!this.providerApiKey) {
      throw new Error(
        "Provider API key is required. Please provide providerApiKey."
      );
    }
  }

  protected getCommandConfig(
    prompt: string,
    mode?: "ask" | "code"
  ): AgentCommandConfig {
    let instruction: string;
    if (mode === "ask") {
      instruction =
        "Research the repository and answer the user's questions. " +
        "Do NOT make any changes to any files in the repository.";
    } else {
      instruction =
        "Do the necessary changes to the codebase based on the users input.\n" +
        "Don't ask any follow up questions.";
    }

    const escapedPrompt = this.escapePrompt(prompt);
    let _prompt = `${instruction}\n\nUser: ${escapedPrompt}`;

    return {
      command: `echo "${this.escapePrompt(_prompt)}" | opencode run${
        this.model ? ` --model ${this.provider}/${this.model} --print-logs` : ""
      }`,
      errorPrefix: "OpenCode",
      labelName: "opencode",
      labelColor: "00D2FF",
      labelDescription: "Pull requests generated by OpenCode agent",
    };
  }

  protected getDefaultTemplate(): string {
    return "vibekit-opencode";
  }

  protected getEnvironmentVariables(): Record<string, string> {
    const envKey = `${this.provider.toUpperCase()}_API_KEY`;
    return {
      [envKey]: this.providerApiKey,
    };
  }

  protected getApiKey(): string {
    return this.providerApiKey;
  }

  protected getAgentType(): AgentType {
    return "opencode";
  }

  protected getModelConfig(): ModelConfig {
    return {
      provider: this.provider,
      apiKey: this.providerApiKey,
      model: this.model,
    };
  }

  // Override generateCode to support history in the prompt
  public async generateCode(
    prompt: string,
    mode?: "ask" | "code",
    branch?: string,
    history?: Conversation[],
    callbacks?: OpenCodeStreamCallbacks,
    background?: boolean
  ): Promise<OpenCodeResponse> {
    let instruction: string;

    if (mode === "ask") {
      instruction =
        "Research the repository and answer the user's questions. " +
        "Do NOT make any changes to any files in the repository.";
    } else {
      instruction =
        "Do the necessary changes to the codebase based on the users input.\n" +
        "Don't ask any follow up questions.";
    }

    const escapedPrompt = this.escapePrompt(prompt);
    let _prompt = `${instruction}\n\nUser: ${escapedPrompt}`;

    if (history) {
      _prompt += `\n\nConversation history: ${history
        .map((h) => `${h.role}\n ${this.escapePrompt(h.content)}`)
        .join("\n\n")}`;
    }

    // Override the command config with history-aware prompt
    const originalGetCommandConfig = this.getCommandConfig.bind(this);
    this.getCommandConfig = (p: string, m?: "ask" | "code") => ({
      ...originalGetCommandConfig(p, m),
      command: `echo "${this.escapePrompt(_prompt)}" | opencode run${
        this.model ? ` --model ${this.provider}/${this.model} --print-logs` : ""
      }`,
    });

    const result = await super.generateCode(
      prompt,
      mode,
      branch,
      history,
      callbacks,
      background
    );

    // Restore original method
    this.getCommandConfig = originalGetCommandConfig;

    return result as OpenCodeResponse;
  }
}
